//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//

#define PININTERFACE
#include <windows.h>
#include <pm.h>
#include <Msgqueue.h>
#include <pwinbase.h>
#include <camera.h>
#include "CameraDriver.h"
#include "PinDriver.h"

CPinDevice :: CPinDevice( )
{
    m_MemoryModel               = CSPROPERTY_BUFFER_CLIENT_LIMITED;
    m_fDiscontinuity            = true ;
    m_PinId                     = -1 ; //Invalid Pin Id
    m_MaxNumOfBuffers           = 2 ;
    m_FrameSize                 = 0 ;
    m_RtAveTimePerFrame         = 0 ;
    m_FramesDropped             = 0 ;
    m_PictureNumber             = 0 ;
    m_hMsgQ                     = NULL ;
    m_CsState                   = CSSTATE_STOP ;
    m_msStart                   = 0xFFFFFFFF;
    m_msLastPT                  = 0;
    m_pStreamDescriptorList     = NULL;
    m_BufferCount               = 0;
    m_pcsDeviceState            = NULL;

    InitializeCriticalSection( &m_csStreamBuffer );
}

CPinDevice :: ~CPinDevice()
{
    ResetBufferList( );

    if ( NULL != m_hMsgQ )
        {
        CloseMsgQueue(m_hMsgQ) ;
        }

    if ( NULL != m_pStreamDescriptorList )
        {
        LocalFree( m_pStreamDescriptorList );
        m_pStreamDescriptorList = NULL;
        }

    m_CsState = CSSTATE_STOP ;
    DeleteCriticalSection(&m_csStreamBuffer) ;   
    m_pcsDeviceState = NULL;
}

bool CPinDevice :: InitializeSubDevice( PCAMERADEVICE pCamDevice )
{

    m_pCamAdapter = pCamDevice ;
    if (NULL == m_pCamAdapter)
        {
        return false ;
        }

    m_pcsDeviceState = m_pCamAdapter->CSDeviceState();
    m_MemoryModel  = static_cast<CSPROPERTY_BUFFER_MODE> (m_pCamAdapter->GetMemoryModel());
    m_MaxNumOfBuffers = m_pCamAdapter->MaxNumOfBuffers();

    return true ;
}

bool 
CPinDevice :: CloseSubDevice()
{
    if (m_pCamAdapter!=NULL)
        {
        CAutoLock cs(m_pcsDeviceState);
        m_pCamAdapter->DecrCInstances( m_PinId ) ;
        }

    return true ;
}

ULONG 
CPinDevice :: PictureNumber( ) const
{
    return m_PictureNumber;
}

ULONG 
CPinDevice :: FramesDropped( ) const
{
    return m_FramesDropped;
}

ULONG CPinDevice :: FrameSize( ) const
{
    return m_FrameSize;
}

DWORD 
CPinDevice :: StreamInstantiate( 
    PCSPROPERTY_STREAMEX_S pCsPropStreamEx, 
    PUCHAR pOutBuf, 
    DWORD  OutBufLen, 
    PDWORD pBytesTransferred )
{
    PCS_DATARANGE_VIDEO pCsDataRangeVid = NULL ;
    DWORD               Error           = ERROR_INVALID_PARAMETER ;
    CAutoLock           cs(m_pcsDeviceState);
    
    if ( -1 != m_PinId )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"Pin %d is already instantiated.\r\n", this, m_PinId 
            )) ;
        goto CleanUp;
        }

    if ( false == m_pCamAdapter->IsValidPin( pCsPropStreamEx->CsPin.PinId ) )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"Invalid Pin Id\r\n", this
            )) ;
        goto CleanUp;
        }
    
    m_PinId = pCsPropStreamEx->CsPin.PinId ;
    
    // Let us set a default format for this pin

    if ( false == m_pCamAdapter->GetPinFormat( m_PinId, 1, &pCsDataRangeVid ) )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"No Pin Format provided for pin\r\n", this
            )) ;
        goto CleanUp;
        }
    
    memcpy(&m_CsDataRangeVideo,pCsDataRangeVid, sizeof(CS_DATARANGE_VIDEO) ) ;

    if ( NULL == pCsPropStreamEx->hMsgQueue )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"NULL Handle provided for msgqueue\r\n", this
            )) ;
        goto CleanUp;
        }
    
    if ( false == m_pCamAdapter->IncrCInstances( m_PinId, this ) )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"Pin %d is already instantiated.\r\n", this, m_PinId
            )) ;
        goto CleanUp;
        }

    MSGQUEUEOPTIONS msgQueueOptions ;
    msgQueueOptions.bReadAccess = FALSE ; // we need write-access to msgqueue
    msgQueueOptions.dwSize      = sizeof(MSGQUEUEOPTIONS);
    
    if ( NULL == (m_hMsgQ = OpenMsgQueue( 
                                GetCallerProcess(),
                                pCsPropStreamEx->hMsgQueue, 
                                &msgQueueOptions ) ) )
        {
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"Failed to open MsgQueue\r\n", this
            )) ;
        m_pCamAdapter->DecrCInstances( m_PinId ) ;
        goto CleanUp;
        }

    return ERROR_SUCCESS ;

CleanUp:

    return Error;
}

void 
CPinDevice :: SetState( 
    CSSTATE CsState, 
    CSSTATE *CsPrevState 
    )
{
    CAutoLock cs(m_pcsDeviceState);

#ifndef __NULL_CAM__ // remember this difference to NULL camera driver
    if ( STILL == m_PinId )
        {
        m_pCamAdapter->PauseCaptureAndPreview( ) ;
        }
#endif

    if ( NULL != CsPrevState )
        {
        *CsPrevState = m_CsState ;
        }

    m_CsState = CsState ; 
    
    switch (CsState)
    {
    case CSSTATE_RUN:   m_pCamAdapter->PDDRun  (m_PinId); break;
    case CSSTATE_STOP:  m_pCamAdapter->PDDStop (m_PinId); break;
    case CSSTATE_PAUSE: m_pCamAdapter->PDDPause(m_PinId); break;
    }

    return ;
}




DWORD 
CPinDevice :: IRQCallback( 
    LPVOID pContext,
    LPVOID pPDDContext
    )
{
    PPINDEVICE pPinDevice = reinterpret_cast<PPINDEVICE>(pContext) ;

    ASSERT( NULL != pPinDevice );

    return pPinDevice->HandlePinIO(pPDDContext) ;
}

BOOL 
CPinDevice::InitMsgQueueDescriptor (
    PCS_MSGQUEUE_BUFFER pCsMsgQBuff, 
    PCS_STREAM_DESCRIPTOR pCsStreamDesc, 
    PVOID pMappedData, 
    PVOID pUnmappedData, 
    BOOL bBufferFill,
    PVOID pPDDContext
    )
{
    PCSSTREAM_HEADER pCsStreamHeader    = reinterpret_cast<PCSSTREAM_HEADER>(pCsStreamDesc) ;
    PCS_FRAME_INFO pCsFrameInfo         = reinterpret_cast<PCS_FRAME_INFO>(pCsStreamHeader + 1) ;
    
    if( ( pCsStreamHeader == NULL ) || 
        ( pCsFrameInfo == NULL )
        )
        {
        CAMMSG(ZONE_ERROR, (L"InitMsgQueueDescriptor(%08x): Invalid Stream Descriptor\r\n", this));
        return false;
        }

    if (bBufferFill)
        {
        // The buffer fill function must use the pointer that's been mapped into this process.
        pCsStreamHeader->Data = pMappedData;

        EnterCriticalSection(&m_csStreamBuffer) ;
        m_pCamAdapter->PDDBufferFill( 
            m_PinId, 
            &m_CsDataRangeVideo.VideoInfoHeader, 
            pCsStreamHeader, 
            pCsFrameInfo,
            pPDDContext 
            ) ;
        LeaveCriticalSection(&m_csStreamBuffer) ;

        pCsFrameInfo->PictureNumber = (LONGLONG)++m_PictureNumber;
        pCsFrameInfo->DropCount     = (LONGLONG)m_FramesDropped;
        }

    // The message queue requires the original pointer value.
    pCsStreamHeader->Data = pUnmappedData;

    // Init the flags to zero
    pCsStreamHeader->OptionsFlags = 0;

    // Set the discontinuity flag if frames have been previously
    // dropped, and then reset our internal flag

    if ( true == m_fDiscontinuity ) 
        {
        pCsStreamHeader->OptionsFlags |= CSSTREAM_HEADER_OPTIONSF_DATADISCONTINUITY;
        m_fDiscontinuity = false ;
        }

    DWORD msNow = GetTickCount();

    if (m_msStart == 0xFFFFFFFF)
        {
        m_msStart = msNow;
        }

    //
    // Return the timestamp for the frame
    //
    pCsStreamHeader->PresentationTime.Numerator      = 1 ;
    pCsStreamHeader->PresentationTime.Denominator    = 1 ;
    pCsStreamHeader->Duration = m_RtAveTimePerFrame ;
    DWORD prevPT = m_msLastPT;
    if (bBufferFill)
        {
        m_msLastPT = msNow - m_msStart;
        pCsStreamHeader->PresentationTime.Time = (LONGLONG) m_msLastPT * 10000;  // presentation time stamp in 100s of ns
        }
    else
        {
        pCsStreamHeader->PresentationTime.Time = 0;
        }

    RETAILMSG(ZONE_PDDINT, (_T("InitMsgQueueDescriptor: LastPT = %d, elapsed = %d\n"), m_msLastPT, m_msLastPT - prevPT));
    
    // clear the timestamp valid flags
    pCsStreamHeader->OptionsFlags &= ~( CSSTREAM_HEADER_OPTIONSF_TIMEVALID | CSSTREAM_HEADER_OPTIONSF_DURATIONVALID );

    // Every frame we generate is a key frame (aka SplicePoint)
    // Delta frames (B or P) should not set this flag

    pCsStreamHeader->OptionsFlags |= CSSTREAM_HEADER_OPTIONSF_SPLICEPOINT;

    pCsMsgQBuff->CsMsgQueueHeader.Size    = sizeof(CS_MSGQUEUE_HEADER);
    pCsMsgQBuff->CsMsgQueueHeader.Flags   = FLAG_MSGQ_FRAME_BUFFER;
    pCsMsgQBuff->CsMsgQueueHeader.Context = NULL;
    pCsMsgQBuff->pStreamDescriptor        = pCsStreamDesc;
    
    RETAILMSG(ZONE_PDDINT, (_T("InitMsgQueueDescriptor(%08x): Frame buf queued: %d (dropped %d), start %d, time %d\n"), 
        this,
        (LONG)pCsFrameInfo->PictureNumber, 
        (LONG)pCsFrameInfo->DropCount, 
        (LONG)m_msStart,
        (LONG)(pCsStreamHeader->PresentationTime.Time / 10000)));

    return TRUE;
   
}

void  
CPinDevice::FlushBufferQueue()
{
    PCS_STREAM_DESCRIPTOR pCsStreamDesc = NULL;
    PVOID                 pMappedData   = NULL;
    PVOID                 pUnmappedData = NULL;
    CS_MSGQUEUE_BUFFER    CsMsgQBuff ;

    DWORD dwSavedPermissions = SetProcPermissions( (DWORD)-1 ) ;

    while (( true == RemoveBufferFromList( &pCsStreamDesc, &pMappedData, &pUnmappedData )) && ( NULL != pCsStreamDesc ) && ( m_hMsgQ != NULL ))
        {
        if (!InitMsgQueueDescriptor (&CsMsgQBuff, pCsStreamDesc, pMappedData, pUnmappedData, FALSE, NULL))
            {
            continue;
            }

        if ( false == WriteMsgQueue( m_hMsgQ, reinterpret_cast<LPVOID>(&CsMsgQBuff),  sizeof(CS_MSGQUEUE_BUFFER), PIN_TIMEOUT, 0 ) )
            {
            DWORD dwErr = GetLastError();
            CAMMSG(ZONE_FUNCTION|ZONE_ERROR, (_T("PIN_Function(%08x): (FlushBufferQueue) WriteMsgQueue returned false 0x%x\r\n"), this, dwErr));
            }
        }

    SetProcPermissions( dwSavedPermissions );

    return;
}

DWORD 
CPinDevice :: HandlePinIO(PVOID pPDDContext)
{
    //assume device locked
    //CAutoLock cs(m_pcsDeviceState);
    DWORD FramesProcessed=0;

    if ( CSSTATE_RUN != m_CsState ) 
        {
        return 0;
        }

#ifdef __NULL_CAM__ // remember this difference to NULL camera driver
    if ( STILL == m_PinId )
        {
        m_pCamAdapter->PauseCaptureAndPreview( ) ;
        }
#endif

    DWORD dwSavedPermissions = SetProcPermissions( (DWORD)-1 ) ;
    
    PCS_STREAM_DESCRIPTOR pCsStreamDesc = NULL;
    PVOID                 pMappedData   = NULL;
    PVOID                 pUnmappedData = NULL;
    CS_MSGQUEUE_BUFFER    CsMsgQBuff ;

    if ( false == RemoveBufferFromList( &pCsStreamDesc, &pMappedData, &pUnmappedData ) || NULL == pCsStreamDesc )
        {
        // We dropped a frame
        m_FramesDropped++;
        m_fDiscontinuity = true;
		//printf("MsgQueue is full\r\n") ;
        goto CleanUp;        
        }

    if (!InitMsgQueueDescriptor (&CsMsgQBuff, pCsStreamDesc, pMappedData, pUnmappedData, TRUE, pPDDContext))
        {
        goto CleanUp;        
        }

    if ( NULL == m_hMsgQ )
        {
        CAMMSG(ZONE_FUNCTION|ZONE_ERROR, (_T("PIN_Function(%08x): MsgQueue is not opened\r\n"), this)) ;
        goto CleanUp;
        }

    if ( false == WriteMsgQueue( m_hMsgQ, reinterpret_cast<LPVOID>(&CsMsgQBuff),  sizeof(CS_MSGQUEUE_BUFFER), PIN_TIMEOUT, 0 ) )
        {
        DWORD dwErr = GetLastError();
        CAMMSG(ZONE_FUNCTION|ZONE_ERROR, (_T("PIN_Function(%08x): (HandlePinIO) WriteMsgQueue returned false error 0x%x\r\n"), this, dwErr));
        }

    if ( STILL == m_PinId )
        {
        m_CsState = CSSTATE_PAUSE;

        m_pCamAdapter->PDDPause  (STILL); 

        m_pCamAdapter->RevertCaptureAndPreviewState( );

        }

    FramesProcessed = 1;
    
CleanUp:

    SetProcPermissions( dwSavedPermissions );

    return FramesProcessed;
}

DWORD CPinDevice ::PauseStream( )
{
    CAutoLock cs(m_pcsDeviceState);

    if( m_CsState == CSSTATE_STOP )
        {
        // Let's allocate our resources
        if( m_pStreamDescriptorList == NULL )
            {
            m_pStreamDescriptorList = (PCS_STREAM_DESCRIPTOR_SHADOW) 
                LocalAlloc( LMEM_ZEROINIT, sizeof( CS_STREAM_DESCRIPTOR_SHADOW ) * m_MaxNumOfBuffers );
            if( NULL == m_pStreamDescriptorList )
                {
                return ERROR_OUTOFMEMORY;
                }
            }

        m_BufferCount = 0;
        }

    m_CsState = CSSTATE_PAUSE ;

    return ERROR_SUCCESS ;
}


DWORD CPinDevice :: PinHandleConnectionRequests( 
        PCSPROPERTY pCsProp, 
        PUCHAR pOutBuf,                 // Unsafe, use with caution
        DWORD  OutBufLen, 
        PDWORD pBytesTransferred      // Unsafe, use with caution
        )
{
    RETAILMSG( ZONE_IOCTL, (_T("PIN_IOControl(%08x): PinHandleConnectionRequests\r\n"), this));
    
    DWORD                           Error                 = ERROR_INVALID_PARAMETER; 
    PCSALLOCATOR_FRAMING            pCsAllocatorFraming     = NULL;
    PCS_DATAFORMAT_VIDEOINFOHEADER  pCsDataFormatVidInfoHdr = NULL;
    PCS_DATAFORMAT_VIDEOINFOHEADER  pCsDataFormatVidInfoHdrCopy = NULL;
    
    if ( NULL == pCsProp )
        {
        return Error;
        }
        
    __try
        {
        *pBytesTransferred = 0 ;
        }
    __except( EXCEPTION_EXECUTE_HANDLER )
        {
        return ERROR_INVALID_PARAMETER;
        }

    // we support PROPSETID_Pin, so just return success
    if ( CSPROPERTY_TYPE_SETSUPPORT == pCsProp->Flags )
        {
        return ERROR_SUCCESS;
        }

    switch ( pCsProp->Id )
    {
    case CSPROPERTY_CONNECTION_STATE:
        Error = PinHandleConnStateSubReqs( pCsProp->Flags, pOutBuf, OutBufLen, pBytesTransferred ) ;
        break ;

    case CSPROPERTY_CONNECTION_DATAFORMAT:
        
        pCsDataFormatVidInfoHdr = (PCS_DATAFORMAT_VIDEOINFOHEADER) pOutBuf;  
        DWORD dwStructSize;
        if(( OutBufLen < sizeof( PCS_DATAFORMAT_VIDEOINFOHEADER )) || ( pOutBuf == NULL ))
            {
            return Error ;
            }
            
        // The DeviceIOControl function is already taking care of calling MapCallerPointer. 
        // The video info header can be modified by the caller while it's being accessed in the subroutine.
        // The Subroutine needs to make a copy of the video info header before accessing it.

        __try
            {
            dwStructSize = sizeof( CS_DATAFORMAT_VIDEOINFOHEADER ) + pCsDataFormatVidInfoHdr->VideoInfoHeader.bmiHeader.biSize - sizeof( CS_BITMAPINFOHEADER );
            pCsDataFormatVidInfoHdrCopy = (PCS_DATAFORMAT_VIDEOINFOHEADER) LocalAlloc( LMEM_ZEROINIT, dwStructSize );
            if( pCsDataFormatVidInfoHdrCopy == NULL )
                {
                return ERROR_OUTOFMEMORY; 
                }
        
            if( CeSafeCopyMemory( pCsDataFormatVidInfoHdrCopy, pCsDataFormatVidInfoHdr, dwStructSize ))
                {  
                Error = PinHandleConnDataFormatSubReqs( pCsProp->Flags, pCsDataFormatVidInfoHdrCopy, pBytesTransferred ) ;
                }
            }
        __except( EXCEPTION_EXECUTE_HANDLER )
            {
            }
        
        LocalFree( pCsDataFormatVidInfoHdrCopy );
        break ;
    
    case CSPROPERTY_CONNECTION_ALLOCATORFRAMING:
        switch ( pCsProp->Flags )
        {
        case CSPROPERTY_TYPE_GET:
        case CSPROPERTY_TYPE_BASICSUPPORT:
            CSALLOCATOR_FRAMING csAllocatorFraming;
            
            if(( OutBufLen < sizeof( CSALLOCATOR_FRAMING )) || ( pOutBuf == NULL ))
                {
                Error = ERROR_MORE_DATA;
                break;
                }

            csAllocatorFraming.RequirementsFlags = GetCurrentMemoryModel();
            csAllocatorFraming.PoolType          = PagedPool;
            if (m_PinId==STILL)
                {
                csAllocatorFraming.Frames            = 1;
                }
            else
                {
                csAllocatorFraming.Frames            = m_MaxNumOfBuffers;
                }
            csAllocatorFraming.FrameSize         = m_CsDataRangeVideo.VideoInfoHeader.bmiHeader.biSizeImage;
            csAllocatorFraming.FileAlignment     = FILE_BYTE_ALIGNMENT;
            csAllocatorFraming.Reserved          = 0;
            
            __try
                {
                memcpy( pOutBuf, &csAllocatorFraming, sizeof( CSALLOCATOR_FRAMING ));
                *pBytesTransferred = sizeof( CSALLOCATOR_FRAMING );
                }
            __except( EXCEPTION_EXECUTE_HANDLER )
                {
                Error = ERROR_INVALID_PARAMETER;
                break;
                }

            Error = ERROR_SUCCESS;
            break ;
        
        case CSPROPERTY_TYPE_SET:
            if( OutBufLen < sizeof( CSALLOCATOR_FRAMING ))
                {
                Error = ERROR_INVALID_PARAMETER;
                break;
                }

            pCsAllocatorFraming = (PCSALLOCATOR_FRAMING) pOutBuf;
            if( ( m_CsState != CSSTATE_STOP ) || 
                ( GetCurrentMemoryModel() != pCsAllocatorFraming->RequirementsFlags ))
                {
                Error = ERROR_INVALID_PARAMETER;
                break;
                }
            m_MaxNumOfBuffers = pCsAllocatorFraming->Frames ;
            Error = ERROR_SUCCESS;
            break ;
        
        default :
            CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
                L"CSPROPERTY_CONNECTION_ALLOCATORFRAMING Invalid Request\r\n", this
                )) ;
        }
        
        break ;
    
    case CSPROPERTY_CONNECTION_PROPOSEDATAFORMAT :
        // no support for dynamic format changes for this driver
        break ;
    
    default :
        CAMMSG(ZONE_IOCTL|ZONE_ERROR, (L"PIN_IOControl(%08x): "
            L"Invalid Request\r\n", this
            )) ;

    }
    
    return Error ;
}

DWORD
CPinDevice::PinHandleBufferRequest(
    DWORD   dwCommand,
    PUCHAR  pOutBuf,        // This is an unsafe parameter, use with caution
    DWORD   OutBufLen
    )
{
    DWORD   Error = ERROR_INVALID_PARAMETER;

    if( ( pOutBuf == NULL ) || 
        ( OutBufLen < sizeof( CS_STREAM_DESCRIPTOR )))
        {
        return Error;
        }


    PCS_STREAM_DESCRIPTOR pCsDescriptor = (PCS_STREAM_DESCRIPTOR) pOutBuf;

    // The pOutBuf argument has already been probed with MapCallerPointer, and the upper layer 
    // has already checked for the size of the buffer to be at least sizeof( CS_STREAM_DESCRIPTOR )

    switch( dwCommand )
    {
        case CS_ALLOCATE:
            Error = AllocateBuffer( pOutBuf );
            break;

        case CS_ENQUEUE:
            Error = EnqueueDescriptor( pOutBuf );
            break;

        case CS_DEALLOCATE:
            Error = DeallocateBuffer( pOutBuf );

        default:
            break;
    }

    return Error;
}


DWORD
CPinDevice::PinHandleConnStateSubReqs(
    ULONG  ulReqFlags,
    PUCHAR pOutBuf,                 // Unsafe, use with caution
    DWORD  OutBufLen,
    PDWORD pBytesTransferred
    )
{
    DWORD    Error  = ERROR_INVALID_PARAMETER;
    PCSSTATE pCsState = NULL;
    CAutoLock cs(m_pcsDeviceState);
    
    switch( ulReqFlags )
    {
    case CSPROPERTY_TYPE_GET:
        if( OutBufLen < sizeof ( CSSTATE ) )
            {
            Error = ERROR_MORE_DATA;
            break;
            }

        if( !CeSafeCopyMemory( pOutBuf, &m_CsState, sizeof( CSSTATE )))
            {
            Error = ERROR_MORE_DATA;
            break;
            }

        // use Error var for copy
        Error = sizeof( CSSTATE );
        if( !CeSafeCopyMemory( pBytesTransferred, &Error, sizeof( DWORD )))
            {
            Error = ERROR_MORE_DATA;
            break;
            }

        Error = ERROR_SUCCESS;
        break;
    
    case CSPROPERTY_TYPE_SET:

        CSSTATE csState;
        if( OutBufLen < sizeof( CSSTATE ))
            {
            Error = ERROR_MORE_DATA;
            break;
            }

        if( !CeSafeCopyMemory( &csState, pOutBuf, sizeof( CSSTATE )))
            {
            Error = ERROR_MORE_DATA;
            break;
            }

        if( csState == m_CsState )
            { 
            Error = ERROR_SUCCESS;
            break;
            }

        switch ( csState )
        {
        case CSSTATE_STOP:

            m_PictureNumber = 0;
            m_FramesDropped = 0;
            m_msLastPT        = 0;

            // We can get to the CSSTATE_STOP state from any other state.
            if ( CSSTATE_STOP == m_CsState ) 
                {
                RETAILMSG( ZONE_IOCTL, ( L"PIN_IOControl(%08x): "
                    L"State to set = CSSTATE_STOP but we are already Stopped.\r\n", this 
                    ));
                Error = ERROR_SUCCESS;
                break;
                }
        
            m_pCamAdapter->PDDStop(m_PinId);

            m_CsState = CSSTATE_STOP;

            // The buffer queue needs to be emptied if the driver is not allocating the buffers
            FlushBufferQueue();
            Error = ERROR_SUCCESS;

            break;

        case CSSTATE_PAUSE:
            
            if ( CSSTATE_PAUSE == m_CsState ) 
                {
                RETAILMSG( ZONE_IOCTL, ( L"PIN_IOControl(%08x): "
                    L"State to set = CSSTATE_PAUSE but we are already Paused.\r\n", this 
                    ));
                Error = ERROR_SUCCESS;
                break;
                }

            m_pCamAdapter->PDDPause(m_PinId);

            Error = PauseStream();

            break;

        case CSSTATE_RUN:
            
            //fail transition from STOP->RUN state.
            if ( CSSTATE_STOP == m_CsState ) 
                {
                CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( L"PIN_IOControl(%08x): "
                    L"CSSTATE_STOP to CSSTATE_RUN is not a supported transition .\r\n", this 
                    ));
                Error = ERROR_INVALID_STATE;
                break;
                }

            // We only allow Still Pin to goto Run state through PROPSETID_VIDCAP_VIDEOCONTROL
            if ( STILL == m_PinId )
                {
                break;
                }

            // it is possible that PDDRun failed.
            if(m_pCamAdapter->PDDRun(m_PinId) != TRUE)
            {
                CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( L"PIN_IOControl(%08x): "
                    L"PDDRun for %d pin failed to work\r\n", this, m_PinId));
                Error = E_FAIL;
            }
            else
            {

                m_CsState = CSSTATE_RUN;
                m_msStart = 0xFFFFFFFF;
                Error = ERROR_SUCCESS;
            }

            break;

        default :
            CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( L"PIN_IOControl(%08x): "
                L"Incorrect State\r\n", this 
                ));
            Error = ERROR_INVALID_PARAMETER;
        }

        break;

    default:
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( L"PIN_IOControl(%08x): "
            L"Invalid Request\r\n", this ));

        break;
    }

    return Error;
}


DWORD
CPinDevice::PinHandleConnDataFormatSubReqs(
    ULONG                          ReqFlags,
    PCS_DATAFORMAT_VIDEOINFOHEADER pCsDataFormatVidInfoHdr,  // Warning: this buffer is unsafe, use with caution
    PDWORD                         pBytesTransferred
    )
{
    DWORD        Error        = ERROR_INVALID_PARAMETER ;
    PCSSTATE    pCsState    = NULL ;

    PCS_DATARANGE_VIDEO pCsDataRangeVideoMatched = NULL ;

    // We must have called IOCTL_STREAM_INSTANTIATE before setting format
    if ( -1 == m_PinId )
        {
        return Error;
        }
    
    // The incoming video info header is unsafe. The data might change on a separate thread
    // while it's being accessed. For security purposes, let's make a copy of the data
    // before any attempt to access them is done, and then work off the copy

    switch( ReqFlags )
    {
    case CSPROPERTY_TYPE_SET:
        if ( true == m_pCamAdapter->AdapterCompareFormat(   m_PinId, 
                                                            pCsDataFormatVidInfoHdr, 
                                                            &pCsDataRangeVideoMatched, 
                                                            true ) 
            )
            {
            // We found our format
            memcpy( &m_CsDataRangeVideo, pCsDataRangeVideoMatched, sizeof ( CS_DATARANGE_VIDEO ) );
            memcpy( &m_CsDataRangeVideo, &pCsDataFormatVidInfoHdr->DataFormat, sizeof ( CSDATARANGE ) );
            memcpy( &m_CsDataRangeVideo.VideoInfoHeader, 
                    &pCsDataFormatVidInfoHdr->VideoInfoHeader, 
                    sizeof ( CS_VIDEOINFOHEADER ) );

            m_RtAveTimePerFrame = m_CsDataRangeVideo.VideoInfoHeader.AvgTimePerFrame;

            m_pCamAdapter->PDDSetFormat( m_PinId,
                &m_CsDataRangeVideo.VideoInfoHeader);

            //*pBytesTransferred = sizeof ( CS_DATARANGE_VIDEO );
            Error = ERROR_SUCCESS;
            }

        break;

    default:
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( L"PIN_IOControl(%08x): "
            L"Invalid Request\r\n", this 
            ));
    }

    return Error;
}


DWORD
CPinDevice::AllocateBuffer( LPVOID pOutBuf )
{
    CAutoLock               cs(&m_csStreamBuffer);
    DWORD                   Error = ERROR_INVALID_PARAMETER;
    DWORD                   dwResult = -1;
    PCS_STREAM_DESCRIPTOR   pCsDescriptor = (PCS_STREAM_DESCRIPTOR) pOutBuf;
    
    // pOutBuf has already been validated through MapCallerPtr in the IOCTL function
    if( ( pOutBuf == NULL ) || 
        ( m_BufferCount >= m_MaxNumOfBuffers ))
        {
        return Error;
        }
    
    // There are 2 cases here: the buffer comes from the hardware or from the software. 
    // If the buffer comes from the software, we generate a new entry in the table up to the maximum allowed.
    // If the buffer comes from the hardware, we setup the application stream descriptor

    // PDD can also allocate additional resources needed for capture, e.g. adjust DMA buffer
    if (!m_pCamAdapter->PDDPrepare( m_PinId, CPinDevice::IRQCallback, this))
        {
        Error = ERROR_OUTOFMEMORY;
        goto CleanUp;
        }

    if( GetCurrentMemoryModel() == CSPROPERTY_BUFFER_DRIVER )
        {
        // Get one of the hardware buffers, and setup the descriptor
        ASSERT( m_pStreamDescriptorList[ m_BufferCount ].m_fBusy == FALSE );

        Error = HwSetupStreamDescriptor( m_BufferCount );
        if( Error != ERROR_SUCCESS )
            {
            goto CleanUp;
            }
            
        if( !CeSafeCopyMemory( pCsDescriptor, &(m_pStreamDescriptorList[ m_BufferCount ].csStreamDescriptorShadow), sizeof( CS_STREAM_DESCRIPTOR )))
            {
            Error = ERROR_INVALID_PARAMETER;
            goto CleanUp;
            }

        m_pStreamDescriptorList[ m_BufferCount ].pCsStreamDescriptorExternal = NULL;
        m_pStreamDescriptorList[ m_BufferCount ].m_fBusy = TRUE;
        m_BufferCount++;
        }
    else if( GetCurrentMemoryModel() == CSPROPERTY_BUFFER_CLIENT_LIMITED )
        {
        // The buffer is allocated by the software, let's copy the descriptor and generate a handle
        ASSERT( m_pStreamDescriptorList[ m_BufferCount ].m_fBusy == FALSE );

        if( !CeSafeCopyMemory( &( m_pStreamDescriptorList[ m_BufferCount ].csStreamDescriptorShadow ), pOutBuf, sizeof( CS_STREAM_DESCRIPTOR )))
            {
            Error = ERROR_INVALID_PARAMETER;
            goto CleanUp;
            }

        // Let's populate the handle and the buffer field.
        Error = SwSetupStreamDescriptor( m_BufferCount, &( m_pStreamDescriptorList[ m_BufferCount ].csStreamDescriptorShadow ), pOutBuf );
        if( Error != ERROR_SUCCESS )
            {
            goto CleanUp;
            }

        m_pStreamDescriptorList[ m_BufferCount ].pCsStreamDescriptorExternal = NULL;
        m_pStreamDescriptorList[ m_BufferCount ].m_fBusy = TRUE;
        m_BufferCount++;
        }
    else if( GetCurrentMemoryModel() == CSPROPERTY_BUFFER_CLIENT_UNLIMITED )
        {
        // let's find a slot available
        DWORD dwAvailableRow = -1;
        for( DWORD i = 0; ( i < m_MaxNumOfBuffers ) && ( dwAvailableRow == -1 ); i++ )
            {
            if( m_pStreamDescriptorList[ i ].m_fBusy == FALSE )
                {
                dwAvailableRow = i;
                }
            }
        if( dwAvailableRow == -1 )
            {
            goto CleanUp;
            }

        if( !CeSafeCopyMemory( &( m_pStreamDescriptorList[ dwAvailableRow ].csStreamDescriptorShadow ), pOutBuf, sizeof( CS_STREAM_DESCRIPTOR )))
            {
            Error = ERROR_INVALID_PARAMETER;
            goto CleanUp;
            }

        Error = SwSetupStreamDescriptor( dwAvailableRow, &( m_pStreamDescriptorList[ dwAvailableRow ].csStreamDescriptorShadow ), pOutBuf );
        if( Error != ERROR_SUCCESS )
            {
            goto CleanUp;
            }

        m_pStreamDescriptorList[ dwAvailableRow ].pCsStreamDescriptorExternal = NULL;
        m_pStreamDescriptorList[ dwAvailableRow ].m_fBusy = TRUE;
        m_BufferCount++;
        }

CleanUp:
    if(Error != ERROR_SUCCESS)
    {
        CAMMSG( ZONE_ERROR, ( _T("CPinDevice::AllocateBuffer failed with 0x%x\r\n"), Error) );
    }
    return Error;
}


DWORD
CPinDevice::DeallocateBuffer( LPVOID pOutBuf )
{
    LPVOID  lpBuffer;
    DWORD   Handle;
    DWORD   Index;
    DWORD   Error = ERROR_SUCCESS;
    PCS_STREAM_DESCRIPTOR pCsDescriptor;
    CAutoLock cs(&m_csStreamBuffer);

    pCsDescriptor = (PCS_STREAM_DESCRIPTOR) pOutBuf;
    lpBuffer = pCsDescriptor->CsStreamHeader.Data;
    Handle = pCsDescriptor->CsStreamHeader.Handle;

    Index = GetIndexFromHandle( Handle, lpBuffer );
    if( Index == -1 )
        {
        Error = ERROR_INVALID_PARAMETER;
        goto CleanUp;
        }

    // If the row is not in use, let's make it available
    if( m_pStreamDescriptorList[ Index ].pCsStreamDescriptorExternal != NULL )
        {
        Error = ERROR_INVALID_PARAMETER;
        goto CleanUp;
        }

    ASSERT( m_pStreamDescriptorList[ Index ].m_fBusy == TRUE );
    m_pStreamDescriptorList[ Index ].m_fBusy = FALSE;
    m_BufferCount--;

    if( GetCurrentMemoryModel() == CSPROPERTY_BUFFER_DRIVER )
        {
        // We release the buffer. 
        RemoteLocalFree( m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader.Data );
        m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader.Data = NULL;
        pCsDescriptor->CsStreamHeader.Data = NULL;
        }

CleanUp:

    // PDD can also allocate additional resources needed for capture
    m_pCamAdapter->PDDUnprepare(m_PinId);

    return Error;
}


DWORD
CPinDevice::SwSetupStreamDescriptor( 
    DWORD                   Index,
    PCS_STREAM_DESCRIPTOR   pCsStreamDesc, 
    LPVOID                  pBuffer             // Warning: This is an unsafe buffer, use with caution
)
{
    DWORD Handle;
    PCS_STREAM_DESCRIPTOR pCsStreamDescExt = ( PCS_STREAM_DESCRIPTOR ) pBuffer;

    if( ( pCsStreamDesc == NULL ) || 
        ( pBuffer == NULL ))
        {
        return ERROR_INVALID_PARAMETER;
        }

    // TODO: In the case the driver has its own buffers, we need to go through the list of internal buffers
    // TODO: Change the handle to a real handle
    Handle = CreateHandle( Index, pBuffer );

    __try
        {
        pCsStreamDescExt->CsStreamHeader.Handle = Handle; 
        }
    __except( EXCEPTION_EXECUTE_HANDLER )
        {
        return ERROR_INVALID_PARAMETER;
        }

    // Note: This is the place to setup DMA for the buffer. 

    pCsStreamDesc->CsStreamHeader.Handle = Handle; 
        
    return ERROR_SUCCESS;
}


DWORD
CPinDevice::HwSetupStreamDescriptor(
    DWORD   Index
)
{
    PCSSTREAM_HEADER      pCsStreamHeader; 
    PCS_FRAME_INFO        pCsFrameInfo;

    if( Index > m_BufferCount )
        {
        return ERROR_INVALID_PARAMETER;
        }

    m_FrameSize = CS_DIBSIZE (m_CsDataRangeVideo.VideoInfoHeader.bmiHeader);

    pCsStreamHeader = &( m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader );
    pCsFrameInfo = &( m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsFrameInfo );

    pCsStreamHeader->Size                         = sizeof(CSSTREAM_HEADER);
    pCsStreamHeader->TypeSpecificFlags            = 0;
    pCsStreamHeader->PresentationTime.Time        = 0;
    pCsStreamHeader->PresentationTime.Numerator   = 1;
    pCsStreamHeader->PresentationTime.Denominator = 1;
    pCsStreamHeader->Duration                     = 0;
    pCsStreamHeader->FrameExtent                  = m_FrameSize;
    pCsStreamHeader->DataUsed                     = m_FrameSize;
    pCsStreamHeader->OptionsFlags                 = CSSTREAM_HEADER_OPTIONSF_DATADISCONTINUITY;

    pCsFrameInfo->ExtendedHeaderSize = sizeof(CS_FRAME_INFO);
    pCsFrameInfo->dwFrameFlags       = CS_VIDEO_FLAG_FRAME;
    pCsFrameInfo->PictureNumber      = 0; 
    pCsFrameInfo->DropCount          = 0;

    // Note: RemoteLocalAlloc can't really trigger an exception, the __try/__except block is here
    // to highlight the fact that this call has to be protected in the case of a hardware access.
    __try
        {
        pCsStreamHeader->Data = RemoteLocalAlloc( LPTR, m_FrameSize );
        }
    __except( EXCEPTION_EXECUTE_HANDLER )
        {
        return ERROR_INTERNAL_ERROR;
        }
    
    if( NULL == pCsStreamHeader->Data )
        {
        return ERROR_OUTOFMEMORY;
        }

    // And setup the handle
    // TODO: Change this to use a real handle
    pCsStreamHeader->Handle = CreateHandle( Index, pCsStreamHeader->Data );

    return ERROR_SUCCESS;
}


DWORD 
CPinDevice::CreateHandle( 
    DWORD  Index, 
    LPVOID pBuffer 
)
{
    DWORD  Handle;
    HANDLE hProcess = GetCallerProcess();

    Handle = (DWORD) hProcess ^ (( Index << 16 ) + ( (DWORD)pBuffer & 0xFFFF ));

    return Handle;
}


DWORD
CPinDevice::GetIndexFromHandle( 
    DWORD  Handle, 
    LPVOID pBuffer      // Warning: This is an unsafe buffer, use with caution
)
{
    DWORD   Index = -1;
    HANDLE  hProcess = GetCallerProcess();

    // TODO: let's retrieve the index from the handle table and make sure we have a match
    Index = ( Handle ^ (DWORD)hProcess ) >> 16;
    if( Index >= m_MaxNumOfBuffers )
        {
        // Invalid index, bail out
        return -1;
        }

    if(   ( m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader.Data   != pBuffer )
        ||( m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader.Handle != Handle ))
        {
        // Something's wrong, bail out
        return -1;
        }

    return Index;
}


DWORD
CPinDevice::EnqueueDescriptor( LPVOID pOutBuf )
{
    LPVOID  lpBuffer, lpMappedBuffer;
    DWORD   Handle;
    DWORD   Index;
    DWORD   Size;
    DWORD   Error = ERROR_INVALID_PARAMETER;
    PCS_VIDEOINFOHEADER   pCsVideoInfoHdr;
    PCS_STREAM_DESCRIPTOR pCsDescriptor;
    CAutoLock cs(&m_csStreamBuffer);
    
    RETAILMSG(ZONE_IOCTL, ( _T("PIN_IOControl(%08x): EnqueueDescriptor\r\n"), this ) );

    if( m_CsState == CSSTATE_STOP )
        {
        return ERROR_SERVICE_NOT_ACTIVE;
        }

    pCsDescriptor = (PCS_STREAM_DESCRIPTOR) pOutBuf;

    // First, let's use the handle and the buffer to retrieve the shadow copy
    // If an exception happens during the following 2 lines, it will be trapped by the upper level
    lpBuffer = pCsDescriptor->CsStreamHeader.Data;
    Handle = pCsDescriptor->CsStreamHeader.Handle;

    // Get the entry for this buffer in the internal list
    Index = GetIndexFromHandle( Handle, lpBuffer );
    if( Index == -1 )
        {
        goto CleanUp;
        }

    // Is the row in use?
    if( m_pStreamDescriptorList[ Index ].m_fBusy == FALSE )
        {
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( _T("PIN_IOControl(%08x): The buffer has not be prepared. Call CS_ALLOCATE first.\r\n"), this ) );
        goto CleanUp;
        }

    if( m_pStreamDescriptorList[ Index ].pCsStreamDescriptorExternal != NULL )
        {
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( _T("PIN_IOControl(%08x): This buffer has already be enqueued.\r\n"), this ) );
        goto CleanUp;
        }

    // Now, let's probe the incoming buffer
    pCsVideoInfoHdr = reinterpret_cast<PCS_VIDEOINFOHEADER>( &m_CsDataRangeVideo.VideoInfoHeader );
    Size = abs( pCsVideoInfoHdr->bmiHeader.biHeight ) * CS_DIBWIDTHBYTES( pCsVideoInfoHdr->bmiHeader );
    lpMappedBuffer = (LPVOID) MapCallerPtr( lpBuffer, Size );

    if( lpMappedBuffer == NULL )
        {
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( _T("PIN_IOControl(%08x): MapCallerPtr failed for incoming pointer \r\n"), this ) );
        goto CleanUp;
        }

    // Finally, set the internal table
    m_pStreamDescriptorList[ Index ].csStreamDescriptorShadow.CsStreamHeader.Data = lpMappedBuffer;
    m_pStreamDescriptorList[ Index ].pCsStreamDescriptorExternal = pCsDescriptor;           

    Error = ERROR_SUCCESS;
    
CleanUp:

    return Error;
}


bool
CPinDevice::RemoveBufferFromList(
    PCS_STREAM_DESCRIPTOR * ppCsStreamDesc,
    PVOID                 * ppMappedData,
    PVOID                 * ppUnmappedData
    )
{
    DWORD Counter = 0;
    CAutoLock cs(&m_csStreamBuffer);

    // Let's look in the list of buffers for the first buffer that has a non null external stream descriptor
    RETAILMSG( ZONE_IOCTL, ( L"PIN_IOControl(%08x): "
        L"RemoveBufferFromList\r\n", this 
        ));

    if(( ppCsStreamDesc == NULL ) || ( ppMappedData == NULL ) || ( ppUnmappedData == NULL ))
        {
        CAMMSG( ZONE_IOCTL|ZONE_ERROR, ( _T("PIN_IOControl(%08x): RemoveBufferFromList - Null pointer has been passed in.\r\n"), this ) );
        return false;
        }

    *ppCsStreamDesc = NULL;

    while(( Counter < m_BufferCount ) && ( *ppCsStreamDesc == NULL ))
        {
        if( m_pStreamDescriptorList[ Counter ].pCsStreamDescriptorExternal != NULL )
            {
            // We found one registered buffer. Let's return it.
            *ppCsStreamDesc = m_pStreamDescriptorList[ Counter ].pCsStreamDescriptorExternal;
            *ppMappedData   = m_pStreamDescriptorList[ Counter ].csStreamDescriptorShadow.CsStreamHeader.Data;
            *ppUnmappedData = m_pStreamDescriptorList[ Counter ].pCsStreamDescriptorExternal->CsStreamHeader.Data;
            m_pStreamDescriptorList[ Counter ].pCsStreamDescriptorExternal = NULL;
            m_pStreamDescriptorList[ Counter ].csStreamDescriptorShadow.CsStreamHeader.Data = *ppUnmappedData;
            }

        Counter++;
        }

    return true;
}


bool
CPinDevice::ResetBufferList()
{
    CAutoLock cs(&m_csStreamBuffer);

    for( DWORD i = 0; i < m_BufferCount; i++ )
    {
        m_pStreamDescriptorList[ i ].pCsStreamDescriptorExternal = NULL;
        m_pStreamDescriptorList[ i ].m_fBusy = FALSE;
    }
    
    return true;
}

CSPROPERTY_BUFFER_MODE 
CPinDevice::GetCurrentMemoryModel()
{
    return m_MemoryModel;
}


